<?php
namespace Pinenut;
/**
 * @author kosh
 *
 */
abstract class Action {
	
	use \Pinenut\Action\Helper\Shield;
	
	/**
	 * Обрабатываемый действием запрос
	 * @var \Pinenut\Request
	 */
	protected $request;
	
	/**
	 * Ответ, возвращается после отыгрывания действия
	 * @var \Pinenut\Response
	 */
	protected $response;
	
	/**
	 * Адрес шаблона оболочки - Layout
	 * 
	 * @var string
	 */
	protected static $layout = null;
	
	/**
	 * Представления действия по умолчанию
	 * в данном случае - используется дефолтное представление
	 * @var array
	 */
	protected static $defaultViews = [''];
	
//	/**
//	 * Генерим ключ по запросу
//	 * по умолчанию - ключ по сериализованному запросу
//	 * 
//	 * Статическим создан для экономии, чтоб экземпляр зря не делать, 
//	 * если из кеша достали готовый ответ
//	 * 
//	 * @param \Pinenut\Request $request
//	 * @return string
//	 */
//	static function key(\Pinenut\Request $request) {
//		return md5(serialize($request->params));
//	}
	
	/**
	 * Возвращает этапы выполнения
	 * Одним из этапов подразумевается действие 
	 * Остальные по умолчанию - представления, натягиваемые одно на другое
	 * @return array
	 */
	static function stages(\Pinenut\Request $request) {
		
		// если лейаут не указан - не подключаем его
		// используется для определения лейаута для действия (не по запросу)
		if (!empty(static::$layout) && $request->withLayout) {
			$layout = array(static::$layout); 
		} else {
			$layout = array();
		}
		
		// если представления запроса не указаны - 
		// используем представление по умолчанию, определенные для действия
		// если ничего не указано - значит просто выполняем действие без представлений
		$views = static::$defaultViews;
		if (null !== $request->view) {
			$views = (array)$request->view;
		} elseif (null === $views) {
			$views = array();
		}
		
		return array_merge(
			array(null), // нулл - значит само действие, как безусловный первый этап выполнения
			$views, // а дальше идёт список представлений
			$layout // и представление лейаута
		);
	}

	function __construct(\Pinenut\Request $request){
		$this->request = $request;
		$this->response = new \Application::$ResponseClass;
	}
	
	/**
	 * Должны вернуть список привилегий, при наличии которых 
	 * у пользователя, ему можно выполнять это действие
	 * по умолчанию - ничего не возвращает, что значит - полный доступ
	 * 
	 * привилегии могут быть как простые, так и утрверждениями (assert), для дополнительной валидации
	 * простые идут как элементы массива, сложные - как элементы ассоциативного массива, 
	 * где названия являются ключами, а значение - ассоциативный массив в котором 
	 * ключ - псевдоним утверждения, которому передаётся значение
	 * 
	 * например для редактирования комментария такую возможность даем модератору, или же хозяину сообщения:
	 * array('comments.moderator', 'comments.creator'=>array('currentUser'=>$creator)
	 * 
	 * @return NULL|string|array
	 */
	function privileges() {
		return null;
	}
	
	
	/**
	 * Выполняем этапы действия
	 * Сначала само действие
	 * Потом все его шаблоны один за другим, модифицируя ответ
	 * 
	 * Возвращаем этап
	 * 
	 * @return /Pinenut/Response
	 */
	function doStage($stage = null, $response = null) {
		if (null !== $response) 
			$this->response = $response;
		
		if (null === $stage) {
		    $this->runStage();
		} else {
			if (!$this->response->noRender()) {
 				$this->render($stage);
			}
		}
		return $this->response;
	}
	
	/**
	 * обёртка для внедрения аспектов
	 * TODO: придумать что-нить с хуками-триггерами-поведениями
	 */
	protected function runStage() {
	    // перехватываем вывод, пакуем в ответ
	    
		ob_start();
	    
	    if ($this->beforeRun()) {
	    	$this->run();
	    	if (!$this->afterRun()) {
	    		$this->response->noRender(true);
	    	}
	    	
	    } else {
	    	$this->response->noRender(true);
	    }
	    
	    $this->response->content .= ob_get_clean();
	}

	/**
	 * Действие
	 * 
	 * Что-то выполняет, выдавая результат в виде основного вывода, 
	 * а так же в виде заполнения объекта ответа
	 * 
	 */
	abstract protected function run();
	
	
	/**
	 * Перед выполнением действия
	 * @return boolean если лож - не выполняем действие
	 */
	protected function beforeRun() {
		return true;
	}
	
	/**
	 * после действия
	 * @return boolean если лож - не выполняем генерацию
	 */
	protected function afterRun() {
		return true;
	}
	
	/**
	 * Представление отображает данные из ответа действия в шаблоне и сохраняет 
	 * результат работы опять в ответ
	 * 
	 * Вызывается диспечером после вызова действия
	 * TODO: вынести в отдельный класс
	 * TODO: реализовать оборачивание самого представления (.phtml) в класс, если не получится, 
	 * то хотя бы в анонимную функцию, для предотвращения повторных инклюдов
	 * 
	 */
	
	protected function render($view = '') {

		// если шаблон явно не указан - берётся одноимённый в том же каталоге
		if (empty($view)) {
			$view = $this->request->action;
		} elseif('#' == substr($view, 0, 1)) {
			$view = substr($view, 1);
		} else {
			$view = $this->request->action . '-' . $view;
		}
		
		$viewFileName = static::prepareViewFileName($view);
		
		// подготовили переменные
		extract($this->response->data);
		$content = $this->response->content;
		ob_start();
		require($viewFileName);
		$this->response->content = ob_get_clean();
	}
	

	/**
	 * Хелпер для вызова действия в контексте текущего
	 * TODO: оформить в подключаемый хелпер (в виде trait)
	 * 
	 * @param string $action имя действия
	 * @param array $params	параметры вызова
	 * @param string|null $view имя шаблона для этого действия
	 * @return \Pinenut\Response
	 */
	function action($action, array $params = array(), $view = '', $deferred = true, $withLayout = false) {
			$dispatcher = \Application::$DispatcherServiceClass;
			
			$action = $this->prepareActionName($action);
			$request = new Request($action, $params, $view, $deferred);
			$request->withLayout = $withLayout;
			
			$response = $dispatcher::action($request);
			return $response;
	}
	
	/**
	 * Хелпер для генерации адреса в контексте текущего
	 * TODO: оформить в подключаемый хелпер (в виде trait)
	 *
	 * @param string $action имя действия
	 * @param array $params	параметры вызова
	 */
	function url($action, array $params = array()) {
		$router = \Application::$RouterServiceClass;
		
		$action = $this->prepareActionName($action);
		
		$url = $router::url($action, $params);
		return $url;
	}
	
	
	function getAuth() {
		return \Application::auth();
	}
	
	function prepareActionName($action) {
		// отрезаем точки, преобразуя относительный адрес к действию в абсолютный
		$points = 0;
		while ('.' == substr($action, 0, 1)) {
			$points++;
			$action = substr($action, 1);
		}
		
		if ($points > 0) {
			// если были точки - отрезаем лишнее у текущего
			// действия и добавляем к нему запрошенное
			$currentAction = explode('.', $this->request->action);
			$currentAction = array_slice($currentAction, 0, -$points);
			$action = implode('.', $currentAction) . '.' . $action;
		}
		
		return $action;
	}
	
	/**
	 * Хелпер действия - редирект
	 * 
	 * TODO: продумать, а то что-то не вижу его пока с кешированием, возможно и не надо...
	 * TODO: делать редирект через респонс
	 * @param string $url
	 */
	function redirect($url) {
		// отрубаем вьюхи и лейаут
		$this->response->noRender(true);
		header('Location: '.$url);
	}
	
	/**
	 * Для управления дополнительными каналами информации необходимо несколько действий, 
	 * сохраняющих команды по работе с этими данными, чтобы впоследствии можно было их накатывать, 
	 * получая ответ из кеш
	 */
	
	/**
	 * Формирование адреса шаблона по имени вьюхи
	 * @param string $view
	 */

	static protected function prepareViewFileName($view) {
		// собираем имя файла
		$path = array_map('static::prepareViewFileNamePart', explode('.', $view));
		return implode('/', $path).'.phtml';
	}
	
	static protected function prepareViewFileNamePart($part) {
		return ucfirst($part);
	}	
}